/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Jan-Hendrik Diederich, Bredex GmbH - bug 201052 * Oakland Software (Francis Upton) <francisu@ieee.org> - bug 223808 * James Blackburn (Broadcom Corp.) - Bug 294628 multiple selection *******************************************************************************/ package org.eclipse.ui.internal.dialogs; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import org.eclipse.core.expressions.EvaluationContext; import org.eclipse.core.expressions.EvaluationResult; import org.eclipse.core.expressions.Expression; import org.eclipse.core.expressions.ExpressionConverter; import org.eclipse.core.runtime.Adapters; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.preference.IPreferencePage; import org.eclipse.jface.preference.PreferenceNode; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.ui.IActionFilter; import org.eclipse.ui.IPluginContribution; import org.eclipse.ui.IWorkbenchPropertyPage; import org.eclipse.ui.IWorkbenchPropertyPageMulti; import org.eclipse.ui.SelectionEnabler; import org.eclipse.ui.internal.IWorkbenchConstants; import org.eclipse.ui.internal.LegacyResourceSupport; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.internal.registry.CategorizedPageRegistryReader; import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants; import org.eclipse.ui.internal.registry.PropertyPagesRegistryReader; import org.eclipse.ui.model.IWorkbenchAdapter; import org.eclipse.ui.plugin.AbstractUIPlugin; /** * This property page contributor is created from page entry in the registry. * Since instances of this class are created by the workbench, there is no * danger of premature loading of plugins. */ public class RegistryPageContributor implements IPropertyPageContributor, IAdaptable, IPluginContribution { private static final String CHILD_ENABLED_WHEN = "enabledWhen"; //$NON-NLS-1$ private String pageId; /** * The list of subpages (immediate children) of this node (element type: * <code>RegistryPageContributor</code>). */ private Collection subPages = new ArrayList(); private boolean adaptable = false; /** * Flag which indicates if this property page supports multiple selection */ private final boolean supportsMultiSelect; private IConfigurationElement pageElement; private SoftReference<Map<String, String>> filterProperties; private Expression enablementExpression; /** * PropertyPageContributor constructor. * * @param pageId * the id * @param element * the element */ public RegistryPageContributor(String pageId, IConfigurationElement element) { this.pageId = pageId; this.pageElement = element; adaptable = Boolean .valueOf( pageElement .getAttribute(PropertyPagesRegistryReader.ATT_ADAPTABLE)) .booleanValue(); supportsMultiSelect = PropertyPagesRegistryReader.ATT_SELECTION_FILTER_MULTI .equals(pageElement.getAttribute(PropertyPagesRegistryReader.ATT_SELECTION_FILTER)); initializeEnablement(element); } @Override public PreferenceNode contributePropertyPage(PropertyPageManager mng, Object element) { PropertyPageNode node = new PropertyPageNode(this, element); if (IWorkbenchConstants.WORKBENCH_PROPERTIES_PAGE_INFO.equals(node.getId())) node.setPriority(-1); return node; } /** * Creates the page based on the information in the configuration element. * * @param element * the adaptable element * @return the property page * @throws CoreException * thrown if there is a problem creating the apge */ public IPreferencePage createPage(Object element) throws CoreException { IPreferencePage ppage = null; ppage = (IPreferencePage) WorkbenchPlugin.createExtension( pageElement, IWorkbenchRegistryConstants.ATT_CLASS); ppage.setTitle(getPageName()); Object[] elements = getObjects(element); IAdaptable[] adapt = new IAdaptable[elements.length]; for (int i = 0; i < elements.length; i++) { Object adapted = elements[i]; if (adaptable) { adapted = getAdaptedElement(adapted); if (adapted == null) { String message = "Error adapting selection to Property page " + pageId + " is being ignored"; //$NON-NLS-1$ //$NON-NLS-2$ throw new CoreException(new Status(IStatus.ERROR, WorkbenchPlugin.PI_WORKBENCH, IStatus.ERROR, message, null)); } } adapt[i] = (IAdaptable) ((adapted instanceof IAdaptable) ? adapted : new AdaptableForwarder(adapted)); } if (supportsMultiSelect) { if ((ppage instanceof IWorkbenchPropertyPageMulti)) ((IWorkbenchPropertyPageMulti) ppage).setElements(adapt); else throw new CoreException( new Status( IStatus.ERROR, WorkbenchPlugin.PI_WORKBENCH, IStatus.ERROR, "Property page must implement IWorkbenchPropertyPageMulti: " + getPageName(), //$NON-NLS-1$ null)); } else ((IWorkbenchPropertyPage) ppage).setElement(adapt[0]); return ppage; } /** * Find an adapted element from the receiver. * * @param element * @return the adapted element or <code>null</code> if it could not be * found. */ private Object getAdaptedElement(Object element) { Object adapted = LegacyResourceSupport.getAdapter(element, getObjectClass()); if (adapted != null) return adapted; return null; } /** * Return the object class name * * @return the object class name */ public String getObjectClass() { return pageElement .getAttribute(PropertyPagesRegistryReader.ATT_OBJECTCLASS); } /** * Returns page icon as defined in the registry. * * @return the page icon */ public ImageDescriptor getPageIcon() { String iconName = pageElement .getAttribute(IWorkbenchRegistryConstants.ATT_ICON); if (iconName == null) return null; return AbstractUIPlugin.imageDescriptorFromPlugin(pageElement .getNamespaceIdentifier(), iconName); } /** * Returns page ID as defined in the registry. * * @return the page id */ public String getPageId() { return pageId; } /** * Returns page name as defined in the registry. * * @return the page name */ public String getPageName() { return pageElement.getAttribute(IWorkbenchRegistryConstants.ATT_NAME); } /** * Calculate whether the Property page is applicable to the current * selection. Checks: * <ul> * <li>multiSelect</li> * <li>enabledWhen enablement expression/li> * <li>nameFilter</li> * <li>custom Filter</li> * <li>checks legacy resource support</li> * </ul> * <p> * For multipleSelection pages, considers all elements in the selection for * enablement. */ @Override public boolean isApplicableTo(Object object) { Object[] objs = getObjects(object); // If not a multi-select page not applicable to multiple selection if (objs.length > 1 && !supportsMultiSelect) return false; if (failsEnablement(objs)) return false; // Test name filter String nameFilter = pageElement .getAttribute(PropertyPagesRegistryReader.ATT_NAME_FILTER); for (Object obj : objs) { object = obj; // Name filter if (nameFilter != null) { String objectName = object.toString(); IWorkbenchAdapter adapter = Adapters.adapt(object, IWorkbenchAdapter.class); if (adapter != null) { String elementName = adapter.getLabel(object); if (elementName != null) { objectName = elementName; } } if (!SelectionEnabler.verifyNameMatch(objectName, nameFilter)) return false; } // Test custom filter if (getFilterProperties() == null) return true; IActionFilter filter = null; // Do the free IResource adapting Object adaptedObject = LegacyResourceSupport .getAdaptedResource(object); if (adaptedObject != null) { object = adaptedObject; } filter = Adapters.adapt(object, IActionFilter.class); if (filter != null && !testCustom(object, filter)) return false; } return true; } /** * Return whether or not object fails the enabledWhen enablement criterea. * For multi-select pages, evaluate the enabledWhen expression using the * structured selection as a Collection (which should be iterated over). * @return boolean <code>true</code> if it fails the enablement test */ private boolean failsEnablement(Object[] objs) { if (enablementExpression == null) return false; try { // If multi-select property page, always pass a collection for iteration Object object = (supportsMultiSelect) ? Arrays.asList(objs) : objs[0]; EvaluationContext context = new EvaluationContext(null, object); context.setAllowPluginActivation(true); return enablementExpression.evaluate( context).equals( EvaluationResult.FALSE); } catch (CoreException e) { WorkbenchPlugin.log(e); return false; } } /** * Returns an object array for the passed in object. If the object is an * IStructuredSelection, then return its array otherwise return a 1 element * Object[] containing the passed in object * * @param obj * @return an object array representing the passed in object */ private Object[] getObjects(Object obj) { if (obj instanceof IStructuredSelection) return ((IStructuredSelection) obj).toArray(); return new Object[] { obj }; } /** * Initialize the enablement expression for this decorator */ protected void initializeEnablement(IConfigurationElement definingElement) { IConfigurationElement[] elements = definingElement .getChildren(CHILD_ENABLED_WHEN); if (elements.length == 0) return; try { IConfigurationElement[] enablement = elements[0].getChildren(); if (enablement.length == 0) return; enablementExpression = ExpressionConverter.getDefault().perform( enablement[0]); } catch (CoreException e) { WorkbenchPlugin.log(e); } } /** * Returns whether the object passes a custom key value filter implemented * by a matcher. */ private boolean testCustom(Object object, IActionFilter filter) { Map<String, String> filterProperties = getFilterProperties(); if (filterProperties == null) return false; for (Entry<String, String> entry : filterProperties.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); if (!filter.testAttribute(object, key, value)) return false; } return true; } /* * @see IObjectContributor#canAdapt() */ @Override public boolean canAdapt() { return adaptable; } /** * Get the id of the category. * * @return String * @since 3.1 */ public String getCategory() { return pageElement .getAttribute(CategorizedPageRegistryReader.ATT_CATEGORY); } /** * Return the children of the receiver. * * @return Collection */ public Collection getSubPages() { return subPages; } /** * Add child to the list of children. * * @param child */ public void addSubPage(RegistryPageContributor child) { subPages.add(child); } private Map<String, String> getFilterProperties() { if (filterProperties == null || filterProperties.get() == null) { Map<String, String> map = new HashMap<>(); filterProperties = new SoftReference<>(map); IConfigurationElement[] children = pageElement.getChildren(); for (IConfigurationElement element : children) { processChildElement(map, element); } } return filterProperties.get(); } /** * Get the child with the given id. * * @param id * @return RegistryPageContributor */ public Object getChild(String id) { Iterator iterator = subPages.iterator(); while (iterator.hasNext()) { RegistryPageContributor next = (RegistryPageContributor) iterator .next(); if (next.getPageId().equals(id)) return next; } return null; } /** * Parses child element and processes it. * * @since 3.1 */ private void processChildElement(Map<String, String> map, IConfigurationElement element) { String tag = element.getName(); if (tag.equals(PropertyPagesRegistryReader.TAG_FILTER)) { String key = element .getAttribute(PropertyPagesRegistryReader.ATT_FILTER_NAME); String value = element .getAttribute(PropertyPagesRegistryReader.ATT_FILTER_VALUE); if (key == null || value == null) return; map.put(key, value); } } @Override public <T> T getAdapter(Class<T> adapter) { if (adapter.equals(IConfigurationElement.class)) { return adapter.cast(getConfigurationElement()); } return null; } /** * @return boolean indicating if this page supports multiple selection * @since 3.7 */ boolean supportsMultipleSelection() { return supportsMultiSelect; } /** * @return the configuration element * @since 3.1 */ IConfigurationElement getConfigurationElement() { return pageElement; } @Override public String getLocalId() { return pageId; } @Override public String getPluginId() { return pageElement.getContributor().getName(); } }